// ==UserScript==
// @name           ShackImageLoader
// @namespace      http://dodob.shackspace.com/greasemonkey/
// @description    Load an image in-line when clicked.
// @include        http://*.shacknews.com/laryn.x?*
// @include        http://shacknews.com/laryn.x?*
// @include        http://*.shacknews.com/frame_laryn.x?*
// @include        http://shacknews.com/frame_laryn.x?*
// ==/UserScript==

/*
	Thanks to ThomW for YouTube Embedinator and ShackLOL.

	2008-12-05
		* Works with shackpics.com/viewer.x links! (Horn)
	2008-10-02
		* Made nice with fukung (colorize for click-embed, but keep original href link)
	2008-10-01
		* Fixed fukung link coloring bug.
	2008-03-17
		* getFullpostParent. Fixes image links within span tags not coloring [load_img].
		* Exported rewriteLinks (contentWindow.shackimgloader_rewriteLinks) for Post Get.
	2008-03-11c
		* Added notRegex exception list for prettified non-hotlinks.
		* Click on right side (15%) of shrunken images for full size.
		* Implemented rudimentary resize on hide.

	2008-03-06
		* Always return X-pos to 0 on image collapse.

	2008-03-04
		* Slightly faster loadImgB-Moved styles to CDATA; innerHTML to gen text

	2008-03-03b
		* Fixed links going off-page after collapsing from within an iframe.
		* Declared a couple of local variables I missed.
		* Renamed namespace (requires uninstall + re-install!)

	@TODO
	* lots of code refactoring (loadImage vs loadAllImages, etc.)
*/

if (contentWindow.script_enabled('image_loader') == "true") {

	function getTime() { benchmarkTimer = new Date(); return benchmarkTimer.getTime(); }
	var benchmarkTimer = null;
	var scriptStartTime = getTime();

	var fukung = /fukung\.net\/v\//i;
	var shackpics = /shackpics\.com\/viewer\.x/;
	function loadImage(event) {
		//GM_log('inside loadImage... ' + this.getAttribute('href'));

		var href = this.getAttribute('href');
		if (href.match(fukung)) { href = href.replace(/http\:\/\/(www\.)?fukung\.net\/v\//, 'http://media.fukung.net/images/'); }
		if (href.match(shackpics)) { href = href.replace(/shackpics\.com\/viewer\.x\?file=/, 'shackpics.com/files/'); }

		// Embed picture
		this.innerHTML = '<img src="' + href + '" '
			+ 'alt=":(" '
			+ 'class="shackimgloader">';

		this.setAttribute('class', 'shackimgloader_o');
		this.removeEventListener('click', loadImage, true);
		this.addEventListener('click', hideImage, true);
		event.stopPropagation();
		event.preventDefault();

		if (!showButton) { return; }

		// If this is the last image loaded, update [load_img] button
		var fullpost = this.parentNode.parentNode;	// div.postbody > div.fullpost

		// Look for any unloaded images, i.e.: a.shackimgloader_x
		var as = getElementByClassName(fullpost, 'a', 'shackimgloader_x');
		//GM_log(fullpost.tagName + ' as is...' + as);

		if (!as) {
			// No unloaded images. Change button to [hide_img]
			var button = document.getElementById('shackimgloader_' + fullpost.parentNode.id.substr(5));
			if (button) {
				if (button.textContent == 'load_img') {
					button.removeEventListener('click', loadAllImages, true);
					button.addEventListener('click', hideAllImages, true);
					button.innerHTML = 'hide_img';
				}
			//} else {
			//	GM_log('There should have been a button here somewhere...');
			}
		}
	}

	function calculateXPos(ele) {
		var totalx = 0;
		var ex = ele;
		while (ex) {
			totalx += ex.offsetLeft;
			ex = ex.offsetParent;
		}
		return totalx;
	}

	function calculateYPos(ele) {
		var totaly = 0;
		var ey = ele;
		while (ey) {
			totaly += ey.offsetTop;
			ey = ey.offsetParent;
		}
		return totaly;
	}

	function xGetFirst(expr, rootEle) {
		var firstItem = document.evaluate(
			expr,
			rootEle,
			null,
			XPathResult.FIRST_ORDERED_NODE_TYPE,
			null).singleNodeValue;
		return firstItem;
	}

	function hideImage(event) {
		//GM_log('inside hideImage... ' + this.getAttribute('href'));

		// Resize.
		var embedImg = xGetFirst('./img[@class="shackimgloader"]', this);
		if (embedImg.width != embedImg.naturalWidth) {
			var win = contentWindow.top;	// called from an iframe, window.pageYOffset = 0

			// Calculate image position
			var imgX = calculateXPos(embedImg);
			//alert('event client x, y: ' + event.clientX + ', ' + event.clientY + '\n'
			//	+ 'event screen x, y: ' + event.screenX + ', ' + event.screenY + '\n'
			//	+ 'pos of img is: ' + imgX + ', ' + imgY);
			var endX = imgX + embedImg.width;
			var zoomAreaX = embedImg.width * 0.15;

			if (event.pageX > (endX - zoomAreaX)) {
				embedImg.style.maxWidth = 'inherit';
				embedImg.style.marginRight = '30px';
				this.parentNode.style.overflow = 'visible';
				event.stopPropagation();
				event.preventDefault();
				return;
			}
		}

		this.innerHTML = this.getAttribute('href');

		this.setAttribute('class', 'shackimgloader_x');
		this.removeEventListener('click', hideImage, true);
		this.addEventListener('click', loadImage, true);
		event.stopPropagation();
		event.preventDefault();

		// Move page position so that link is in view
		var win = contentWindow.top;	// called from an iframe, window.pageYOffset = 0
		var totaly = calculateYPos(this);
		//GM_log('>>> link Y: ' + totaly + '. >>> page Y: ' + win.pageYOffset);
		if (totaly < win.pageYOffset) {
			//GM_log('*** link out of sight ***');
			win.scrollTo(0, totaly - (win.innerHeight * 0.15));	// minus room for context
			//win.scrollByLines(-1);	// give the link some room for visual context
		}

		if (!showButton) { return; }

		// If this is the last image hidden, call hideAllImages to update [hide_img] button
		var fullpost = this.parentNode.parentNode;	// div.postbody > div.fullpost
		var as = getElementByClassName(fullpost, 'a', 'shackimgloader_o');

		if (!as) {
			// No loaded images. Change button to [load_img]
			var button = document.getElementById('shackimgloader_' + fullpost.parentNode.id.substr(5));
			if (button) {
				if (button.textContent == 'hide_img') {
					button.removeEventListener('click', hideAllImages, true);
					button.addEventListener('click', loadAllImages, true);
					button.innerHTML = 'load_img';
				}
			//} else {
			//	GM_log('There should have been a button here somewhere...');
			}
		}
	}

	// Go up tree and get div.fullpost
	function getFullpostParent(a) {
		var pParent = a.parentNode;
		while (pParent) {
			if (pParent.className.indexOf('fullpost') != -1) { break; }
			pParent = pParent.parentNode;
		}
		return pParent;
	}

	// Evaluate an array of <a> anchors and change them
	function rewriteLinks(a) {
		for (var i = 0; i < a.length; i++) {

			var href = a[i].getAttribute('href');
			if (!href) { continue; }

			var imgRegex = /\/[^:?]+\.(jpg|jpeg|png|gif|bmp)$/i;
			//var notRegex = /fukung\.net\/v\//i;
			//if (!href.match(imgRegex) || href.match(notRegex)) { continue; }
			if (!href.match(imgRegex)&&!href.match(shackpics)) { continue; }

			// Set attr on the fullpost DIV so that CSS will color the load/hide button -- adds about 10ms?
			var fullpost = getFullpostParent(a[i]);	//a[i].parentNode.parentNode;
			if (fullpost.className.indexOf('shackimgloader_img') == -1) {
				fullpost.setAttribute('class', fullpost.className + ' shackimgloader_img'); 
			}

			//GM_log('image link match: ' + a[i]);
			if (!autoLoad) {
				a[i].addEventListener('click', loadImage, true);
				a[i].setAttribute('class', ' shackimgloader_x');

			} else {
				if (href.match(fukung)) { href = href.replace(/http\:\/\/(www\.)?fukung\.net\/v\//, 'http://media.fukung.net/images/'); }
				if (href.match(shackpics)) { href = href.replace(/shackpics\.com\/viewer\.x\?file=/, 'shackpics.com/files/'); }
					a[i].innerHTML = '<img src="' + href + '" '
					+ 'alt=":(" '
					+ 'class="shackimgloader">';

				a[i].setAttribute('class', 'shackimgloader_o');
				a[i].removeEventListener('click', loadImage, true);
				a[i].addEventListener('click', hideImage, true);
			}

		}
	}

	// Install [load_img] button. Expects the <LI> element of id = item_<threadId>
	function installLoadImgButton(threadEle) {

		var threadId = threadEle.id.substr(5);
		// Run on the right document when called from the iframe
		if (contentWindow != contentWindow.top) {
			document = contentWindow.top.document;
		}

		// Find where to position the button
		//var spanAuth = getElementByClassName(threadEle, 'span', 'author');
		var spanAuth = getElementByClassName(threadEle, 'div', 'postdate');
		if (!spanAuth) {
			if (GM_log) { GM_log('We couldn\'t find a place for the button (' + threadId + ')'); }
			return false;
		}

		// Check if button is already there. This takes 0ms! 
		if (getElementByClassName(spanAuth, 'div', 'shackimgloader_t')) {
			if (GM_log) { GM_log('There already is a button in here (' + threadId + ')!'); }
			return false;
		}

		// Create el [load_img] button!
		var d = document.createElement('div');
		d.setAttribute('class', 'shackimgloader_t');

		// [
		d.appendChild(document.createTextNode('['));

		// load_img
		var a = document.createElement('a');
		a.id = 'shackimgloader_'+threadId;
		a.innerHTML = 'load_img';
		a.addEventListener('click', loadAllImages, true);
		d.appendChild(a);

		// ]
		d.appendChild(document.createTextNode(']'));

		// Append button
		spanAuth.appendChild(d);
	}

	//
	function loadAllImages(event) {
		// Run on the right document when called from the iframe
		if (contentWindow != contentWindow.top) {
			document = contentWindow.top.document;
		}

		// Find the item_<threadId>
		var topEle = document.getElementById(this.id.replace('shackimgloader_', 'item_'));

		// Find the fullpost. The first match is the one we're looking for.
		var fullpostDiv = getElementByClassName(topEle, 'div', 'fullpost');
		
        var a = fullpostDiv.getElementsByClassName('shackimgloader_x');

		for (var i = 0; i < a.length; i++) {
			var href = a[i].getAttribute('href');
			if (href.match(fukung)) { href = href.replace(/http\:\/\/(www\.)?fukung\.net\/v\//, 'http://media.fukung.net/images/'); }
			if (href.match(shackpics)) { href = href.replace(/shackpics\.com\/viewer\.x\?file=/, 'shackpics.com/files/'); }
			a[i].innerHTML = '<img src="' + href + '" '
				+ 'alt=":(" '
				+ 'class="shackimgloader">';

			a[i].setAttribute('class', 'shackimgloader_o');
			a[i].removeEventListener('click', loadImage, true);
			a[i].addEventListener('click', hideImage, true);
		}

		// Change button to hide
		if (a.length > 0) {
			this.removeEventListener('click', loadAllImages, true);
			this.addEventListener('click', hideAllImages, true);
			this.innerHTML = 'hide_img';
		}

		event.stopPropagation();
		event.preventDefault();
	}

	function getElementsByClassName(ele, strTagName, strClassName) {
		var a = ele.getElementsByTagName(strTagName);
		var b = new Array();
		for (var i = 0; i < a.length; i++) {
			if (a[i].className == strClassName) {
				b.push(a[i]);
			}
		}
		return b;
	}

	// Return first element of specified type and class encountered in ele
	function getElementByClassName(ele, strTagName, strClassName) {
		var a = ele.getElementsByTagName(strTagName);
		for (var i = 0; i < a.length; i++) {
			//GM_log(a[i].className + a[i].tagName);
			//if (a[i].className.indexOf(strClassName) == 0) {
			if (a[i].className.indexOf(strClassName) != -1) {
				return a[i];
			}
		}
	}

	//
	function hideAllImages(event) {

		// Run on the right document when called from the iframe
		if (contentWindow != contentWindow.top) {
			document = contentWindow.top.document;
		}

		// Save old location of button (used for collapsing)
		var win = contentWindow.top;	// if called from an iframe, window.pageYOffset = 0
		var oldY = calculateYPos(this);
		var oldPageYOffset = win.pageYOffset;

		// Find the item_<threadId>
		var topEle = document.getElementById(this.id.replace('shackimgloader_', 'item_'));

		// Find the fullpost. The first match is the one we're looking for.
		var fullpostDiv = getElementByClassName(topEle, 'div', 'fullpost');
		
		var a = getElementsByClassName(fullpostDiv, 'a', 'shackimgloader_o');

		for (var i = 0; i < a.length; i++) {
			a[i].innerHTML = a[i].getAttribute('href');
			a[i].setAttribute('class', 'shackimgloader_x');
			a[i].removeEventListener('click', hideImage, true);
			a[i].addEventListener('click', loadImage, true);
		}

		// Change button to load
		if (a.length > 0) {
			this.removeEventListener('click', hideAllImages, true);
			this.addEventListener('click', loadAllImages, true);
			this.innerHTML = 'load_img';
		}
		
		event.stopPropagation();
		event.preventDefault();

		// Collapse around the [hide_img]->[load_img] button
		var newY = calculateYPos(this);

		// Do collapsing if [load_img] button is not visible (Move page)
		if (newY < win.pageYOffset) {
			//GM_log('new Y is less than pageYOffset');
			//GM_log('new:' + newX + ','+newY
			//		+ '. old:' + oldX + ',' + oldY
			//		+ '. diff:' + (newX - oldX) + ',' + (newY - oldY) + '.');

			// Compensate for oldY > length of new page (where Firefox will scroll up by x amount)
			// also catches if (oldY > document.body.clientHeight)
			var offsetY = 0;
			if (win.pageYOffset != oldPageYOffset) {	// page has been moved by firefox
				//GM_log('*** window has been moved by ' + (oldPageYOffset - win.pageYOffset) + '***');
				offsetY = oldPageYOffset - win.pageYOffset;
			}

			win.scrollBy(0 - win.pageXOffset, newY - oldY + offsetY);
		}
	}


	// Start here.

	var autoLoad;
	var showButton;

	//autoLoad = GM_getValue('autoLoad', false);
	//showButton = GM_getValue('showButton', true);
	autoLoad = false;	// autoLoad breaks some button behavior
	showButton = true;	// displaying buttons is very slow!

	// Override show_item_fullpost, called when browsing through a thread
	if (!contentWindow.shackimgloader_show_item_fullpost) {

		// Rename original function and redefine
		contentWindow.shackimgloader_show_item_fullpost = contentWindow.show_item_fullpost;
		contentWindow.show_item_fullpost = function(root_id, article_id, fullpost_element) {
			//GM_log('*** From within shackimgloader_show_item_fullpost ***');

			// Call original function
			contentWindow.shackimgloader_show_item_fullpost(root_id, article_id, fullpost_element);

			// Evaluate all anchors found in fullpost_element (no sense in looking for div.postbody)
			rewriteLinks(fullpost_element.getElementsByTagName('a'));
			if (showButton) { installLoadImgButton(fullpost_element.parentNode); }
		};
	}

	// Export Imageloader rewriteLinks out of Greasemonkey to unsafe space.
	if (!contentWindow.shackimgloader_rewriteLinks) {
		contentWindow.shackimgloader_rewriteLinks = function(ele) { rewriteLinks(ele); };
	}

	if (location.pathname == '/laryn.x') {
		
		// Add CSS properties. Compared to inline, GM_addStyle allows us to specify hover color on links.
		// CDATA hack from http://d.hatena.ne.jp/brazil/20060820/1156056851
		GM_addStyle("img.shackimgloader {\
				max-width: 99%;\
				border-width: 0px !important;\
			}\
			div.shackimgloader_t {\
				display: inline;\
				float: none;\
				padding-left: 4px;\
				font-size: 10px;\
			}\
			div.shackimgloader_t a {\
				color: rgb(102, 102, 102) !important;\
				cursor: text !important;\
				padding: 0 0.12em;\
				text-decoration: none !important;\
			}\
			a:link.shackimgloader_o, a:active.shackimgloader_o, a:visited.shackimgloader_o,\
			a:link.shackimgloader_x, a:active.shackimgloader_x, a:visited.shackimgloader_x,\
			div.shackimgloader_img div.postdate div.shackimgloader_t a {\
				color: rgb(122, 142, 205) !important;\
				cursor: pointer !important;\
			}\
			a:hover.shackimgloader_o, a:hover.shackimgloader_x,\
			div.shackimgloader_img div.postdate div.shackimgloader_t a:hover {\
				color: rgb(172, 192, 255) !important;\
				cursor: pointer !important;\
			}\
			div.line {\
				overflow: visible !important;\
			}");
		
		// Find all postbody divs
		var divNodes = document.evaluate(
			'//div[@class="postbody"]', //'//div[@class="fullpost"]',
			document,
			null,
			XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
			null);

		// Collect all anchors from div.postbody's
		var as = new Array();
		for (var i = 0, div = null; div = divNodes.snapshotItem(i); i++) {
			var a2 = div.getElementsByTagName('a');
			for (var j = 0; j < a2.length; j++) {
				as.push(a2[j]);
			}

			// Place [img] buttons at every div.postbody -- this bumps load time from 31ms to 313ms!!
			if (showButton) { installLoadImgButton(div.parentNode.parentNode); }
		}

		// Change attributes of anchors found
		rewriteLinks(as);

	} else if (location.pathname == '/frame_laryn.x') {
		
		// frame_laryn.x is used when LOADING posts in a thread (loads content, replaces iframe, etc.)

		// No need to override show_item_fullpost again

		// Override function used for Refresh Thread button. Is this called from /laryn.x too?
		if (!contentWindow.shackimgloader_replace_whole_element_from_iframe) {

			contentWindow.shackimgloader_replace_whole_element_from_iframe = contentWindow.replace_whole_element_from_iframe;
			contentWindow.replace_whole_element_from_iframe = function(id) {
				//GM_log('.:. From frame_laryn.x :: replace_whole_element_from_iframe .:.');

				// Call original function
				contentWindow.shackimgloader_replace_whole_element_from_iframe(id);

				// Set document
				var doc = contentWindow.top.document;

				// doc.evaluate XPath divNodes included postbody divs from ALL the document,
				//   not just from childrenNodes of doc.getElementById(id) we wanted.

				//GM_log(id);	// id is the root post. "root_<id>"

				// Find the item_<threadId>
				var topEle = doc.getElementById(id.replace('root_', 'item_'));
				//GM_log('topEle is ' + topEle.id + ' : ' + topEle.tagName);

				// Find all postbodys within the thread
				var divs = getElementsByClassName(topEle, 'div', 'postbody');

				// Find all anchors within divs
				var as = new Array();
				for (var i = 0; i < divs.length; i++) {
					var a2 = divs[i].getElementsByTagName('a');
					
					// Copy anchors found into outside array
					for (var j = 0; j < a2.length; j++) {
						//GM_log(a2[j]);
						as.push(a2[j]);
					}

					if (showButton) { installLoadImgButton(divs[i].parentNode.parentNode); }
				}

				// Evaluate and change attributes of anchors found
				rewriteLinks(as);
			};
		}

	}

	if (GM_log) { GM_log((getTime() - scriptStartTime) + 'ms'); }

}
